<html>
  <head>
    <title>组件基础</title>
  </head>
  <body>
    <script>
      // 组件基础
              // 一、基本示例
                          // 下面是一个Vue组件的示例：
                                Vue.component('button-counter',{
                                    data:function(){
                                      return{
                                        count:0
                                      }
                                    },
                                    template:'<button v-on:click="count++">You clicked me {{count}} times</button>'
                                })

                          /*
                              组件时可复用的Vue实例，且带有一个名字。

                              我们可以在一个通过 new Vue 创建的Vue根实例中，把这个组件作为自定义元素来使用。

                          */ 
                                /*
                                      <div id="components-demo">
                                        <button-counter></button-counter>
                                      </div>
                                */

                                      new Vue({
                                        el:'#components-demo'
                                      });
                            /*
                                组件是"可复用的Vue实例"，所以它们与 new Vue() 接收相同的选项。
                                例如  data、computed、watch、methods、生命周期钩子等。

                                仅有的例外是像 el 这样根实例特有的选项。
                            */


               // 二、组件的复用
                            // 你可以将组件进行任意次数的复用
                                /*
                                    <div id="components-demo">
                                        <button-counter></button-counter>
                                        <button-counter></button-counter>
                                        <button-counter></button-counter>
                                    </div>  
                                */

                             // ★、data 必须是一个函数
                                    /*
                                        当我们定义这个<button-counter>组件时，你可能会发现它的data并不像Vue根实例一样直接提供一个对象：
                                    */
                                          data:{
                                              count:0
                                          }

                                    /*  
                                        取而代之的是，"一个组件的data选项必须使一个函数"
                                        因此每个实例可以"维护一份"  "被返回对象的独立拷贝"：
                                    */
                                         // eg:
                                         data:function(){
                                          return{
                                            count:0
                                          }
                                         }

                                      // 如果Vue没有这条规则，操作组件实例就会影响到其他按钮组件的实例。
                   
                                      

                // 三、组件的组织
                              /*
                                   通常一个应用会以一个"嵌套的组件树"的形式来组织组件：

                                   为了能够在模板中使用，这些组件必须先注册以便Vue能够识别。
                                   这里有两种组件的注册类型：
                                         1、全局注册
                                                  全局注册的组件可以用在其被注册后的任何新创建的Vue根实例中，也包括其组件树中的所有子组件的模板中。

                                         2、局部注册
                              */



                // 四、通过 prop 向子组件传递数据
                              /*
                                    早些时候，我们提到了创建一个博文组件的事情。
                                    问题是如果你不能向这个组件传递一篇博文的标题或内容之类的我们想展示的数据的话，它是没有办法使用的。
                                    这也是prop的由来。
                                    
                                    Prop是你可以在组件上注册的一些自定义属性。
                                    当一个值传递给 prop 的　attribute 的时候，它就变成了那个组件实例的一个 property属性。
                              */
                               
                              // 为了给博文组件传递一个标题，我们可以用一个 props 选项将其包含在该组件可接受的 prop列表中：
                                     
                                         Vue.component('blog-post',{
                                            props:['title'],
                                            template:'<h3>{{titile}}</h3>'
                                         })

                              /*
                                  一个组件默认可以拥有任意数量的　prop, 任何值都可以传递给任何 prop.

                                  在上述模板中，你会发现我们能够在组件实例中访问这个值，就像访问 data 中的值一样。
                              */

                              // 一个 prop 被注册之后，你就可以像这样吧数据作为一个自定义 attribute 传递进来
                                          /*
                                                <blog-post title="My journey with Vue"></blog-post>
                                                <blog-post title="Blogging with Vue"></blog-post>
                                                <blog-post title="Why Vue is so fun"></blog-post>
                                          */

                              // 然而在一个典型的应用中，你可能在data里有一个博文的数组：
                                         new Vue({
                                            el:'#blog-post-demo',
                                            data:{
                                                 posts:[
                                                    {
                                                      id:1,title:'My journey with Vue'
                                                    },
                                                    {
                                                      id:2,title:'Blogging with Vue'
                                                    },
                                                    {
                                                      id:3,title:'Why Vue is so fun'
                                                    }
                                                 ]
                                            }

                                         });

                                // 并想为每篇文章渲染一个组件：

                                          /*
                                                <blog-post
                                                  v-for="post in posts"
                                                  v-bind:key="post.id"
                                                  v-bind:title="post.title"
                                                ></blog-post>
                                          */

                                          /*
                                              如上所述，你会发现我们可以使用 v-bind 来动态传递 prop  。
                                              这在你一开始不清楚要渲染的具体内容，比如"从一个API获取博文列表"的时候，是非常有用的。
                                          */


                // 五、单个根元素

                            // 当构建一个 <blog-post>组件时，你的模板最终会包含的东西远远不止一个标题：

                                      /*
                                          <h3>{{title}}</h3>
                                      */


                            // 最起码，你会包含这篇博文的正文：

                                      /*
                                          <h3>{{title}}</h3>\
                                          <div v-html="context"></div>
                                      */

                            /*
                                 然而如果你在模板中尝试这样写，Vue会显示一个错误，并解释道：  
                                      every component must have a single root element （每个组件必须只有一个根元素）
                                    
                                  你可以将模板的内容包裹在一个父元素内，来修复这个问题：
                            */
                                      /*
                                          <div class="blog-post">
                                            <h3>{{ title }}</h3>
                                            <div v-html="content"></div>
                                          </div>
                                      */

                            /*
                                  看起来当组件变得越来越复杂的时候，我们的博文不只需要标题和内容，还需要发布日期、评论等等。
                                  为每个相关的信息定义一个prop会变得很麻烦：
                            */
                                        /*
                                            <blog-post
                                              v-for="post in posts"
                                              v-bind:key="post.id"
                                              v-bind:title="post.title"
                                              v-bind:content="post.content"
                                              v-bind:publishedAt="post.publishedAt"
                                              v-bind:comments="post.comments"
                                            ></blog-post>
                                        */
                            // 所以，是时候重构一下这个 <blog-post>组件了，让它变成接受一个短度的 post prop
                                        /*
                                            <blog-post
                                              v-for="post in posts"
                                              v-bind:key="post.id"
                                              v-bind:post="post"
                                            ></blog-post>
                                        */

                                        Vue.component('blog-post',{
                                          props:['post'],
                                          template:`
                                            <div class="blog-post">
                                                <h3>{{post.title}}</h3>
                                                <div v-html="post.content"></div>
                                            </div>
                                          `
                                        });

                            // 现在，不论何时为 post 对象添加一个新的 property,它都会自动地在 <blog-post>内可用。

              
              // 六、监听"子组件事件"
                            /*
                                在我们开发 <blog-post>组件时，它的一些功能可能要求我们和父级组件进行沟通。
                                
                                例如，我们可能会引入一个辅助功能来放大博文的字号，同时然页面的其他部分保持默认的字号。
                            */
                                // 在其父组件中，我们可以通过添加一个 postFontSize 数据 property来支持这个功能：

                                      new Vue({
                                        el:'#blog-post-events-demo',
                                        data:{
                                          posts:[/*......*/],
                                          postFontSize:1
                                        }
                                      });

                                // 它可以在模板中用来控制所有博文的字号：
                                      /*
                                          <div id="blog-posts-events-demo">
                                            <div :style="{ fontSize: postFontSize + 'em' }">
                                              <blog-post
                                                v-for="post in posts"
                                                v-bind:key="post.id"
                                                v-bind:post="post"
                                              ></blog-post>
                                            </div>
                                          </div>
                                      */

                                // 现在我们在每篇博文正文之前添加一个按钮来放大字号：
                                      Vue.component('blog-post',{
                                           props:['post'],
                                           templare:`
                                                <div class="bolg-post">
                                                     <h3>{{post.title}}</h3>
                                                     <button>
                                                        Enlarge text
                                                     </button>
                                                     <div v-html="post.content"></div>
                                                </div>
                                           `
                                      });

                                // 问题是这个按钮不会做任何事

                                /*
                                    当点击这个按钮时，我们需要告诉父级组件放大所有的博文的文本。
                                    幸好，Vue实例提供了 "自定义事件系统" 来解决这个问题。
                                */

                                //  父级组件可以像处理 native DOM 事件一样，通过 v-on 指令监听子组件实例的任意事件：

                                      /*
                                            <blog-post  v-on:enlarge-text="postFontSie+=0.1">

                                            </blog-post>
                                      */

                                // 同时"子组件"可以通过调用内建的 $emit('事件名') 方法，并传入事件名来触发事件

                                          Vue.component('blog-post',{
                                                    props:['post'],
                                                    templare:`
                                                          <div class="bolg-post">
                                                              <h3>{{post.title}}</h3>
                                                              <button v-on:click="$emit('enlarge-text')">
                                                                  Enlarge text
                                                              </button>
                                                              <div v-html="post.content"></div>
                                                          </div>
                                                    `
                                          });

                                         // 有了这个  v-on:enlarge-text="postFontSize+=0.1" 监听器，父组件就会接收该事件并更新 postFontSize的值
                          
                          
                            // ★、使用事件抛出一个值
                                   /*
                                      有的时候用一个事件来抛出来一个特定的值是十分有用的。（其实就是传递一个"事件参数"）

                                      例如，我们可能想让<blog-post>组件决定它的文本要放大多少。
                                   */

                                   // 这时候可以使用 $emit() 方法的第二个参数来提供这个值
                                           // eg:
                                                /*
                                                    <button v-on:click="$emit('enlarge-text',0.1)">
                                                        Enlarge Text
                                                    </button>
                                                */

                                   // 然后当在父组件监听这个事件的时候，我们可以通过 $event 访问到被传递的值：
                                            /*
                                                <blog-post v-on:enlarge-text="postFontSize +=$event">

                                                </blog-post>
                                            */

                                  // 或者通过"事件处理函数"来接收这个值
                                            /*
                                                <blog-post v-on:enlarge-text="onEnlargeRext">

                                                </blog-post>
                                            */

                                            // 那么，这个值将会作为第一个参数传入这个方法：

                                            methods:{
                                                onEnlargeText:function(enlargeAmount){
                                                    this.postFontSize +=enlargeAmount
                                                }
                                            }


                            // ★、在组件上使用 v-model
                                     // 自定义事件也可以用于创建"支持v-model"的自定义输入组件

                                         // 记住：
                                                  /*
                                                      <input v-model="searchText">
                                                  */
                                                  // 等价于
                                                  /*
                                                      <input 
                                                          v-bind:value="searchText"
                                                          v-on:input="searchText"
                                                      >
                                                  */

                                                  // 当用在组件上时， v-model 则会这样：
                                                       /*
                                                          <custom-input 
                                                                  v-bind:value="searchText"
                                                                  v-on:input="searchText= $event"
                                                          >
                                                          </custom-input>
                                                       */

                                          /*
                                              为了让它正常工作，这个组件内的<input> 必须：

                                                    1、将其 value attribute 属性绑定到一个名为  value 的 prop上
                                                    2、 在其input 事件被触发时，将新的值通过自定义的input 事件抛出(事件参数)
                                          */ 
                                                  // 写成代码之后是这样的：

                                                  Vue.component('custom-input',{
                                                      props:['value'],
                                                      template:`
                                                          <input 
                                                              v-bind:value="value"
                                                              v-on:input="$emit('input',$event.target.value)"
                                                      `
                                                  })

                                                  // 现在 v-model 就应该可以在这个组件上完美的工作起来了。

                                                      // <custom-input v-model="searchText"></custom-input>

                // 七、通过插槽分发内容

                          // 和 html 元素一样，我们经常需要向一个组件传递内容，就像这样：
                                /*
                                      <alert-box>
                                          Something bad happend!
                                      </alert-box>
                                */

                          // 可能会渲染出这样的东西：

                                //  Error！ Something bad happened!

                          // 幸好， Vue自定义的 "<solt>元素" 让这变得非常简单：

                                Vue.component('alert-box',{
                                    template:`
                                        <div class="demo-alert-box">
                                            <strong>Error!</strong>
                                            <slot></slot>
                                        </div>
                                    `
                                })

                                // 如你所见，我们只要在需要的地方加入"插槽"就行了---就这么简单。


                // 八、动态组件

                            /*
                                有的时候，在不同组件之间进行动态切换时非常有用的，比如在 elementUI中的 tabs标签页组件。
                            */

                            /*
                                上述内容可以通过Vue的 <component>元素加一个特殊的 is 组件属性来实现：
                            */
                                    /*
                                        <!-- 组件会在 'currentTabComponennt'改变时改变-->
                                        <component v-bind:is="currentTabComponent"></component>
                                    */

                                    /*
                                        上面的模板中， currentComponent 可以包括：、
                                            1、已注册的组件的名称
                                            2、一个组件的选项对象
                                    */


                // 九、解析DOM模板时的注意事项
                            /*
                                 有些HTML元素，诸如 <ul>、<ol>、<table> 和 <select>,对于哪些元素可以出现在其内部是由严格限制的。
                                
                                 而有些元素， 诸如 <li> 、<tr> 和 <option> 只能出现在其他某些特定的元素内部。
                            */

                            // 这会导致我们使用这些有约束条件的元素时遇到一些问题：
                                    /*
                                        <table>
                                            <blog-post-row></blog-post-row>
                                        </table>
                                    */

                            /*
                                 这个自定义组件<blog-post-row>会被作为无效的内容提升到外部，并导致最终渲染结果出错。
                                        
                                 幸好，这个特殊的 is 组件属性给了我们一个变通途的方法：
                            */
                                    /*
                                        <table>
                                            <tr is="blog-post-row"></tr>
                                        </table>
                                    */

                              /*
                                  需要注意的是如果我们从以下来源使用模板的话，这条限制是不存在的：

                                        1、字符串（例如： template:'......'）
                                        2、单文本组件 (.vue)
                                        3、<script type="text/x-template">
                              */
   </script>
  </body>
</html>